# Custom theme

This article will tell you how to develop custom theme.

## Extensions based on the default theme

In most cases, you don't want to develop a theme from scratch, but want to extend it based on the default theme. At this time, you can refer to the following methods for theme development.

:::tip
If you want to develop a custom theme from scratch, you can go to [Redevelop a custom theme](/ui/custom-theme#redevelop-a-custom-theme).
:::

### 1. Basic structure

By default, you need to create a `theme` directory under the project root directory, and then create an `index.ts` or `index.tsx` file under the `theme` directory, which is used to export the theme content.

```txt
└── theme
    └── index.tsx
```

You can write the `theme/index.tsx` file as follows:

```tsx title="theme/index.tsx"
import { Layout as BasicLayout } from '@rspress/core/theme';

const Layout = () => <BasicLayout beforeNavTitle={<div>some content</div>} />;

export { Layout };

export * from '@rspress/core/theme';
```

On the one hand, you need to export a theme configuration object through `export`, on the other hand, you need to export all named exported content through `export * from '@rspress/core/theme'` so as to ensure your theme works fine.

### 2. Use slot

It is worth noting that the `Layout` component has designed a series of props to support slot elements. You can use these props to extend the layout of the default theme. For example, change the above `Layout` component to the following form:

```tsx title="theme/index.tsx"
import { Layout as BasicLayout } from '@rspress/core/theme';

// Show all props below
const Layout = () => (
  <BasicLayout
    /* Before home hero */
    beforeHero={<div>beforeHero</div>}
    /* After home hero */
    afterHero={<div>afterHero</div>}
    /* Before home features */
    beforeFeatures={<div>beforeFeatures</div>}
    /* After home features */
    afterFeatures={<div>afterFeatures</div>}
    /* Before doc footer */
    beforeDocFooter={<div>beforeDocFooter</div>}
    /* After doc footer */
    afterDocFooter={<div>afterDocFooter</div>}
    /* Doc page front */
    beforeDoc={<div>beforeDoc</div>}
    /* Doc page end */
    afterDoc={<div>afterDoc</div>}
    /* Doc content front */
    beforeDocContent={<div>beforeDocContent</div>}
    /* Doc content end */
    afterDocContent={<div>afterDocContent</div>}
    /* Before the nav bar */
    beforeNav={<div>beforeNav</div>}
    /* Before the title of the nav bar in the upper left corner */
    beforeNavTitle={<span>😄</span>}
    /* Nav bar title */
    navTitle={<div>Custom Nav Title</div>}
    /* After the title of the nav bar in the upper left corner */
    afterNavTitle={<div>afterNavTitle</div>}
    /* The right corner of the nav menu */
    afterNavMenu={<div>afterNavMenu</div>}
    /* Above the left sidebar */
    beforeSidebar={<div>beforeSidebar</div>}
    /* Below the left sidebar */
    afterSidebar={<div>afterSidebar</div>}
    /* Above the right outline column */
    beforeOutline={<div>beforeOutline</div>}
    /* Below the outline column on the right */
    afterOutline={<div>afterOutline</div>}
    /* Top of the entire page */
    top={<div>top</div>}
    /* Bottom of the entire page */
    bottom={<div>bottom</div>}
    /* Custom MDX components */
    components={{ p: (props) => <p {...props} className="my-4 leading-7" /> }}
  />
);

export { Layout };
// re-export
export * from '@rspress/core/theme';
```

### 3. Custom home page and 404 page

In addition to the slot method, if you want to extend the default theme components, you can also customize the Home page components and 404 page components,
as well as other Rspress [built-in components](https://github.com/web-infra-dev/rspress/tree/main/packages/theme-default/src/components)

```tsx title="theme/index.tsx"
import {
  Search as BasicSearch,
  Layout as BasicLayout,
} from '@rspress/core/theme';

// Custom Home Page
const HomeLayout = () => <div>Home</div>;
// Custom 404 page
const NotFoundLayout = () => <div>404</div>;
// Use slot
const Layout = () => (
  <BasicLayout
    beforeNavTitle={<div>some content</div>}
    HomeLayout={HomeLayout}
    NotFoundLayout={NotFoundLayout}
  />
);

// Custom Search Component
const Search = () => (
  <div className="my-search">
    <BasicSearch />
  </div>
);
export { Search, HomeLayout, NotFoundLayout };
// re-export
export * from '@rspress/core/theme';
```

Of course, you may need to use page data during the development process, you can get it through [`Runtime API`](/api/client-api/api-runtime).

### 4. Custom icon

If you want to modify the icons used in the default theme component individually, just put the icons with the same name in the theme/assets directory.

```txt
└── theme
    └── assets
        └── logo.svg
```

You can view all the icons used in the default theme [here](https://github.com/web-infra-dev/rspress/tree/main/packages/theme-default/src/assets).

## Redevelop a custom theme

If you're developing a custom theme from scratch, you need to understand the basic structure of the theme and the runtime API.

### 1. Basic structure

By default, you need to create a `theme` directory under the project root directory, and then create an `index.ts` or `index.tsx` file under the `theme` directory, which is used to export the theme content.

```txt
├── theme
│   └── index.tsx
```

In the `theme/index.tsx` file, you need to export a Layout component, which is the entry component of your theme:

```tsx
// theme/index.tsx
import { Layout as BasicLayout } from '@rspress/core/theme';

function Layout() {
  return <BasicLayout />;
}

// Export Layout component and setup function
export { Layout };
// Export all content of the default theme to ensure that your theme configuration can work properly
export * from '@rspress/core/theme';
```

### 2. Runtime API

#### usePageData

Get information about all data on the site, such as:

```tsx
import { usePageData } from '@rspress/core/runtime';

function MyComponent() {
  const pageData = usePageData();
  return <div>{pageData.title}</div>;
}
```

#### useLang

Get the current language information, such as:

```tsx
import { useLang } from '@rspress/core/runtime';

function MyComponent() {
  const lang = useLang();
  return <div>{lang}</div>;
}
```

#### Content

Get MDX component content, such as:

```tsx
import { Content } from '@rspress/core/runtime';

function Layout() {
  return (
    <div>
      <Content />
    </div>
  );
}
```

#### Route hook

[react-router-dom](https://reactrouter.com/) is used inside Rspress to implement routing, so you can directly use the Hook of `react-router-dom`, for example:

```tsx
import { useLocation } from '@rspress/core/runtime';

function Layout() {
  const location = useLocation();
  return <div>Current location: {location.pathname}</div>;
}
```

### 3. Reusing search functionality

The default theme comes with built-in search functionality, which we can break down into two components:

1. The search box, i.e., the entry point to invoke the search.
2. The search panel that pops up after clicking on the search box.

#### Full reuse

If you want to fully reuse the search functionality, you can directly import the `Search` component, like so:

```tsx
import { Search } from '@rspress/core/theme';

function MySearch() {
  return <Search />;
}
```

#### Reusing the search panel

If you only want to reuse the search panel and customize the search box part, then you need to import the `SearchPanel` component in your theme component, like so:

```tsx
import { useState } from 'react';
import { SearchPanel } from '@rspress/core/theme';

function MySearch() {
  const [focused, setFocused] = useState(false);
  return (
    <div>
      <button onClick={() => setFocused(true)}>Toggle Search</button>
      <SearchPanel focused={focused} setFocused={setFocused} />
    </div>
  );
}
```

In this case, you need to maintain the `focused` state and `setFocused` method yourself, and pass them as props to the `SearchPanel` component for controlling the display and hiding of the search panel.

#### Reuse default full text search logic

If you want to reuse the default full text search logic, you can use the `useFullTextSearch` Hook, for example:

```tsx
import { useFullTextSearch } from '@rspress/core/theme';

function MySearch() {
  const { initialized, search } = useFullTextSearch();

  useEffect(() => {
    async function searchSomeKeywords(keywords: string) {
      if (initialized) {
        // Search for keywords
        const results = await search(keywords);
        console.log(results);
      }
    }
    searchSomeKeywords('keyword');
  }, [initialized]);

  return <div>Search</div>;
}
```

Here, `initialized` indicates whether the search is initialized, the `search` method is used to search for keywords, returning a Promise, the result of the Promise is the result of the default full text search.

It should be noted that the `useFullTextSearch` Hook will automatically load the search index during initialization, so you need to first determine the `initialized` status, ensure that initialization is complete, and then call the `search` method.

The type definition of the search method is as follows:

```ts
type SearchFn = (keywords: string, limit?: number) => Promise<SearchResult>;
```

The limit represents the maximum number of search results, default is 7, which means by default it returns a maximum of seven article results.
